fix(tooling): derive the prerequisite gate name by stripping the module extension, not only .mjs - #16670
Merged
baozhoutao merged 1 commit intoSep 7, 2026
Conversation
…le extension, not only `.mjs` The refusal frame prints two different strings: the gate's NAME in the headline and the PATH the reader runs. The name was derived with a `.mjs`-only strip written when every importer was `.mjs`, so the first TypeScript importer (`scripts/check-exported-any-returns.mts`) printed its extension in the one message a reader is meant to quote back. The extension comes off, and the rule is now stated rather than left as a regex: `GATE_MODULE_EXTENSIONS` declares the two module extensions this repo's gate corpus uses, and `gateNameOf` removes whichever one the file has. The test that settles the direction is what the reader has to type, and it does not point at the extension either way -- the runnable string is `command`, which keeps the real path with its real extension, and the way a reader re-runs the gate is its package script (`check:exported-any-returns`), which has no extension at all. The PATH half is deliberately unmoved: stripping it would hand the reader a file that does not exist. Five self-test cases pin the pair on one fixture -- a `.mts` gate and a `.mjs` sibling differing only in extension -- so neither half can drift without a red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
baozhoutao
marked this pull request as ready for review
September 7, 2026 17:52
baozhoutao
enabled auto-merge
September 7, 2026 17:52
This was referenced Sep 7, 2026
baozhoutao
deleted the
claude/issue-15864-prerequisite-gate-name-extension
branch
September 7, 2026 18:39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #15864
Re-derived on
origin/mainat0a61db1f5d: the strip is atscripts/import-prerequisite.mjs:883(the dispatch's reading of 883 holds), and the TypeScript consumer population is one —scripts/check-exported-any-returns.mts. My own re-derivation counts 61 real importers at that tip (60.mjs+ 1.mts), not the 62 the dispatch carried; the one-TypeScript-consumer premise is unaffected.The decision, and why — triage asked for this explicitly
Decision: the module extension comes off, for
.mtsexactly as it always did for.mjs. Not reached by widening the regex to/\.m[jt]s$/— reached by applying triage's own test, what does the reader actually have to type, which turns out to point away from the extension in both directions:command(fromimporterCommandPath) is the path. The path half already carries the real extension —node scripts/check-exported-any-returns.mts— and is already correct. The reader copies that line, not the headline..mtsgate's real invocation ispnpm --filter @objectstack/client check:exported-any-returns, which runs it throughtsx, notnode— so the file extension is not even the reader's entry point.So the alternative reading — keep the extension, and call the
.mjsgates the inconsistent ones — fails its own test, and it would also change what every importer prints. That is the 61-consumer output change the card's stop condition names as a different card, and it is not what landed here.Per triage's "make the rule explicit rather than extend the strip", the rule is now stated rather than encoded in a regex:
GATE_MODULE_EXTENSIONSdeclares the two module extensions this repo's gate corpus uses (matching the paircheck-ratchet-remedy-authority.mjsalready declares for the same reason), andgateNameOfremoves whichever one the file has.Does anything read the derived name programmatically? — swept, answer is no
Triage forbade assuming this away. Swept five reader classes; one real reader exists and it does not key on the name:
scripts/check-regen-pending.mjs:474—gateCouldNotRunmatches the refusal with(\S+): PREREQUISITE NOT METand does capture the identifier. But\S+matches both spellings, and the captured value is only re-printed in a detail line; nothing branches on it. Measured rather than read: feeding it both spellings returns byte-identicalkind,headlineandfix, differing only in the display string. Cosmetic stays cosmetic./tmp/NAME.logis a command handed to a human; nothing in the tree reads such a file. Both spellings are writable paths..mtsconsumer's identifier.check-ratchet-remedy-authority.mjs, whose table has acheck-exported-any-returns.mtskey, keys on script filenames (CORPUS_EXTENSIONS = ['.mjs', '.mts']), not on this derived name.On the dispatch's lead about independent copies of the frame:
check-dts-closure.mjs,check-i18n-bundles.mjs,check-i18n-coverage.mjsandcheck-published-readme-exports.mjseach spell their ownprerequisiteNotMetText, but every one takes a message and hard-codes its own name literal — none derives a name from a path. This PR fixes the helper, which is the only place the derivation exists; "everywhere it is spelled" is not a different outcome here because the other spellings have no derivation to fix.Before / after — paired, same tree, same run, no
node_modulesExit code captured before any pipe in every reading. Both are exit 3 before and after; the refusal still refuses.
Before (at
0a61db1f5d):After:
The control is what makes it a discrimination: the
.mjssibling is byte-identical before and after, so the.mtsline moved because of the rule, not because of the run.The half that deliberately did not move — the runnable path keeps its real extension:
(the log sink follows the name and drops the extension; both spellings are writable, and no
.mjs/.mtsbasename collision exists in the gate corpus — theX.mjs+X.d.mtspairs in the tree are type declarations, which hold no runtime code and never import this module).Tests
Five new self-test cases, pinned as a pair on one fixture (a
.mtsgate and a.mjssibling differing only in extension), because a single observation cannot tell "the extension was stripped" from "there was never one to strip". They cover both halves: the name loses the extension, and the command keeps it.node scripts/import-prerequisite.mjs --self-test-> exit 0, 76 cases pass (71 before)..mjs-only list, mutation proven on disk by before/aftergrep -c, restored under a trap and verified by blob hashef5f4df360…plus an emptygit diff HEAD): exit 1, 2 of 76 fail — precisely the two name-reading cases, while the two command cases and the.mjscontrol stayed green. Direction predicted before running and matched.35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Two initially returned exit 3 (PREREQUISITE NOT MET— the very frame this card is about, read as NOT MEASURED, never a pass); cleared by building the@objectstack/lint/@objectstack/formulaclosures throughscripts/pm/os-verify-lock.shand re-run to exit 0.eslint . --no-inline-config: 6309 files received by eslint, 0 errors, 0 warnings, exit 0 — run at final commit, so no narrowing needed and none claimed.Changeset
skip-changeset—scripts/import-prerequisite.mjsis repo-internal tooling at the repo root; no published package ships it (verified against every non-private manifest'sfiles), so nothing is released by this change. Label applied at PR-open time.🤖 Generated with Claude Code
https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
Generated by Claude Code